﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Notoric.Model.Utils
{
    /// <summary>
    /// An implementation of the <see cref="IEnumerable{T}"/> interface that can
    /// be used to enumrate through parts of a collection.
    /// </summary>
    /// <typeparam name="T">Type of the collection elements</typeparam>
    public class CollectionPart<T> : IEnumerable<T>
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CollectionPart{T}"/> instance.
        /// </summary>
        /// <param name="collection">
        /// <para>
        /// Type: <see cref="IList{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The enumerated collection
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="collection"/> is <see langword="null"/>.
        /// </exception>
        public CollectionPart(IList<T> collection)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            this.collection = collection;
            this.start = 0;
            this.stop = collection.Count;
        }

        #endregion

        #region IEnumerable<T> Members

        /// <inheritdoc/>
        public IEnumerator<T> GetEnumerator()
        {
            return new CollectionPartEnumerator<T>(collection, start, stop);
        }

        #endregion

        #region IEnumerable Members

        /// <inheritdoc/>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new CollectionPartEnumerator<T>(collection, start, stop);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Sets the enumerated part of the collection.
        /// </summary>
        /// <param name="start">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element in the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <param name="end">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element after the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> or <paramref name="end"/> are less than 0 or greater than the number of elements in the collection.<br/>
        /// -or-<br/>
        /// <paramref name="end"/> is less than <paramref name="start"/>.
        /// </exception>
        public void SetBounds(int start, int end)
        {
            if (start < 0 || start > collection.Count)
                throw new ArgumentOutOfRangeException("start");
            if (end < 0 || end > collection.Count || end < start)
                throw new ArgumentOutOfRangeException("end");

            this.start = start;
            this.stop = end;
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the enumerated collection.
        /// </summary>
        private IList<T> collection;
        /// <summary>
        /// Holds the index of the first element in the enumerated part.
        /// </summary>
        private int start;
        /// <summary>
        /// Holds the index of the first element after the enumerated part.
        /// </summary>
        private int stop;

        #endregion
    }

    /// <summary>
    /// An implementation of the <see cref="IEnumerator{T}"/> interface that can be used
    /// to enumerate through parts of a collection.
    /// </summary>
    /// <typeparam name="T">Type of the collection elements.</typeparam>
    public struct CollectionPartEnumerator<T> : IEnumerator<T>
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CollectionPartEnumerator{T}"/> instance.
        /// </summary>
        /// <param name="collection">
        /// <para>
        /// Type: <see cref="IList{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The enumerated collection
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <see langword="null"/>.
        /// </exception>
        public CollectionPartEnumerator(IList<T> collection)
            : this()
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            this.collection = collection;
            start = 0;
            stop = collection.Count;
            current = -1;
        }

        /// <summary>
        /// Creates a new <see cref="CollectionPartEnumerator{T}"/> instance.
        /// </summary>
        /// <param name="collection">
        /// <para>
        /// Type: <see cref="IList{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The enumerated collection
        /// </para>
        /// </param>
        /// <param name="start">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element in the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <param name="end">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element after the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> or <paramref name="end"/> are less than 0 or greater than the number of elements in the collection.<br/>
        /// -or-<br/>
        /// <paramref name="end"/> is less than <paramref name="start"/>.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <see langword="null"/>.
        /// </exception>
        public CollectionPartEnumerator(IList<T> collection, int start, int end)
            : this()
        {
            if (collection == null)
                throw new ArgumentNullException("collection");

            this.collection = collection;
            SetBounds(start, end);
        }

        #endregion

        #region IEnumerator<T> Members

        /// <inheritdoc/>
        public T Current
        {
            get { return collection[current]; }
        }

        #endregion

        #region IDisposable Members

        /// <inheritdoc/>
        public void Dispose()
        {
            collection = null;
        }

        #endregion

        #region IEnumerator Members

        /// <inheritdoc/>
        object System.Collections.IEnumerator.Current
        {
            get { return collection[current]; }
        }

        /// <inheritdoc/>
        public bool MoveNext()
        {
            if (current >= stop - 1)
                return false;

            ++current;
            return true;
        }

        /// <inheritdoc/>
        public void Reset()
        {
            current = start - 1;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Sets the enumerated part of the collection.
        /// </summary>
        /// <remarks>
        /// Calling this method resets the enumerator, so <see cref="MoveNext"/> must be called
        /// before accessing the first element.
        /// </remarks>
        /// <param name="start">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element in the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <param name="end">
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// Index of the first element after the enumerated part of the collection.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="start"/> or <paramref name="end"/> are less than 0 or greater than the number of elements in the collection.<br/>
        /// -or-<br/>
        /// <paramref name="end"/> is less than <paramref name="start"/>.
        /// </exception>
        public void SetBounds(int start, int end)
        {
            if (start < 0 || start > collection.Count)
                throw new ArgumentOutOfRangeException("start");
            if (end < 0 || end > collection.Count || end < start)
                throw new ArgumentOutOfRangeException("end");

            this.start = start;
            this.stop = end;
            current = start - 1;
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the enumerated collection.
        /// </summary>
        private IList<T> collection;
        /// <summary>
        /// Holds the index of the current element.
        /// </summary>
        private int current;
        /// <summary>
        /// Holds the index of the first element in the enumerated part.
        /// </summary>
        private int start;
        /// <summary>
        /// Holds the index of the first element after the enumerated part.
        /// </summary>
        private int stop;

        #endregion
    }
}
